package com.yuan.springcloud.user.aspect;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import com.alibaba.fastjson.JSON;
import com.yuan.springcloud.common.exception.ErrorEnum;
import com.yuan.springcloud.common.utils.HttpContextUtil;
import com.yuan.springcloud.user.bean.open.OpenApiReq;
import com.yuan.springcloud.user.bean.open.OpenResult;
import com.yuan.springcloud.user.valid.SignValidInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 开放平台api签名验证切面
 * @author YouYuan
 * @date 2022/11/21 2:16
 */
@Aspect
@Component
@Slf4j
public class OpenApiAspect {

    private static final Map<String, String> APPID_KEY_MAP = new HashMap<>();
    private static final Map<String, Long> APPID_ACCOUNT_MAP = new HashMap<>();

    private static long SIGN_TIMEOUT_MS = 10 * 1000;//签名超时时间,10秒
    static {
        APPID_KEY_MAP.putIfAbsent("app2022112101", "75c2d3cedd3fa418ad98fca63ececbe6");
        APPID_ACCOUNT_MAP.putIfAbsent("app2022112101", 100001L);
    }

    @Pointcut("execution(public * com.yuan..controller.open.*.*(..))")
    public void signPoint() {
    }

    @Around("signPoint()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        HttpServletRequest request = HttpContextUtil.getRequest();
        String method = null;
        String servletPath = null;
        if (request != null) {
            method = request.getMethod();
            servletPath = request.getServletPath();
        }
        String classAndMethod = getClassAndMethod(joinPoint);
        Object[] args = joinPoint.getArgs();
        log.info("[req openApi {}] Args->:{}", classAndMethod, argsToStr(args));
        SignValidInfo validInfo = null;
        try {
            validInfo = validSign(args);
        } catch (IllegalAccessException e) {
            return OpenResult.error(ErrorEnum.SING_ERR, e.getMessage());
        }
        log.info("[req openApi {}] validInfo->:{}", classAndMethod, validInfo);
        Object result;
        try {
            result = joinPoint.proceed();
            log.info("proceed result:{}", result);
            processServerSign(result, validInfo);
        } catch (Throwable e) {
            log.error("OpenApiAspect proceed error", e);
            return OpenResult.error(e.getMessage());
        }
        return result;
    }

    private void processServerSign(Object result, SignValidInfo validInfo) {
        if (result instanceof OpenResult) {
            OpenResult resp = (OpenResult) result;
            String appId = validInfo.getAppId();
            String randomStr = IdUtil.simpleUUID();
            long timestamp = System.currentTimeMillis();
            String appKey = validInfo.getAppKey();
            long serverTime = System.currentTimeMillis();
            String serverSign = execSign(appId, randomStr, appKey, timestamp);
            resp.setSign(serverSign);
            resp.setRandomStr(randomStr);
            resp.setTimestamp(timestamp);
        }
    }

    private SignValidInfo validSign(Object[] args) throws IllegalAccessException {
        if (args != null && args.length > 0) {
            for (Object arg : args) {
                if (arg instanceof OpenApiReq) {
                    OpenApiReq req = (OpenApiReq) arg;
                    String appId = req.getAppId();
                    String sign = req.getSign();
                    String randomStr = req.getRandomStr();
                    Long timestamp = req.getTimestamp();
                    String appKey = APPID_KEY_MAP.get(appId);
                    long serverTime = System.currentTimeMillis();
                    if (serverTime - timestamp > SIGN_TIMEOUT_MS) {
                        throw new IllegalAccessException("签名已过期，请重试");
                    }
                    if (StrUtil.isEmpty(randomStr) || randomStr.length() < 32) {
                        throw new IllegalAccessException("无效的签名");
                    }
                    if (StrUtil.isEmpty(appKey)) {
                        throw new IllegalAccessException("无效的appId");
                    }
                    String serverSign = execSign(appId, randomStr, appKey, timestamp);
                    if (Objects.equals(serverSign, sign)) {
                        SignValidInfo validInfo = new SignValidInfo();
                        validInfo.setAppId(appId);
                        validInfo.setAppKey(appKey);
                        validInfo.setAccountId(APPID_ACCOUNT_MAP.get(appId));
                        req.setAccountId(validInfo.getAccountId());
                        return validInfo;
                    } else {
                        throw new IllegalAccessException("签名错误");
                    }
                }
            }
        }
        throw new IllegalAccessException("签名验证失败");
    }

    private String execSign(String appId, String randomStr, String appKey, Long timestamp) {
        AES aes = SecureUtil.aes(HexUtil.decodeHex(appKey));
        String data = appId + "&" + randomStr + "&" + timestamp;
        return aes.encryptHex(data);
    }

    @AfterReturning(returning = "rs", pointcut = "signPoint()")
    public void doAfter(JoinPoint joinPoint, Object rs) {
        String classAndMethod = getClassAndMethod(joinPoint);
        log.info("[{} Done] Return->:{}", classAndMethod, JSON.toJSONString(rs));
    }

    private String getClassAndMethod(JoinPoint joinPoint) {
        return joinPoint.getSignature().getDeclaringType().getSimpleName() +
                "." + joinPoint.getSignature().getName();
    }


    public static String argsToStr(Object[] args) {
        try {
            StringBuilder sb = new StringBuilder();
            if (args != null && args.length > 0) {
                for (int i = 0; i < args.length; i++) {
                    if (i != 0) {
                        sb.append(" |,| ");
                    }
                    if (args[i] == null) {
                        sb.append("null");
                    } else {
                        if (args[i].getClass().equals(byte[].class)) {
                            byte[] bb = (byte[]) args[i];
                            sb.append("bytes[").append(bb == null ? "0" : bb.length).append("]");
                        } else if (args[i] instanceof MultipartFile) {
                            sb.append("MultipartFile");
                        } else if (args[i] instanceof MultipartFile[]) {
                            sb.append("MultipartFileArr");
                        } else if (args[i] instanceof HttpServletRequest) {
                            sb.append("HttpServletRequest");
                        } else if (args[i] instanceof HttpServletResponse) {
                            sb.append("HttpServletResponse");
                        } else if (args[i] instanceof HttpSession) {
                            sb.append("HttpSession");
                        } else {
                            sb.append(JSON.toJSONString(args[i]));
                        }
                    }
                }
            } else {
                sb.append("null");
            }
            return sb.toString();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }

    }

    public static void main(String[] args) {
        // 随机生成密钥
//        byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue()).getEncoded();
        String appKey = APPID_KEY_MAP.get("app2022112101");
        System.out.println("appKey:" + appKey);
        // 构建
        AES aes = SecureUtil.aes(HexUtil.decodeHex(appKey));

        String content = "app2022112101&" + IdUtil.simpleUUID() + "&" + (System.currentTimeMillis() + 6000000);

        // 加密为16进制表示
        String encryptHex = aes.encryptHex(content);
        System.out.println("encryptHex:" + encryptHex);
        // 解密为字符串
        String decryptStr = aes.decryptStr(encryptHex, CharsetUtil.CHARSET_UTF_8);
        System.out.println("decryptStr:" + decryptStr);

        String enc = "f6c7111422227232c15b99edf4a1e7dd6f72bdd3d11b9e3462517a80f1fbfad86b1e726a1e0b8f85f59394552b91fe73976003304a7a2a13fda28d2b175b044e";
        System.out.println("enc decryptStr:" + aes.decryptStr(enc, CharsetUtil.CHARSET_UTF_8));

    }
}
